
/**
 * This class implements a Jumping Beast monster 
 */
public class JumpingBeast extends Monster {
    //#if IMGGROUP_12x12 || IMGGROUP_12x12xLimited
        /** "Speed" of monster mooving */
        static final byte STEP = 3;
        /** The distance within monster "sees" Marv */
        static final short DISTANCE_ATTACKING = 47;
        static final short DISTANCE_EVADING = 73;
        static final byte TURNING_CORRECTION_STEP = 5;
    //#elif IMGGROUP_18x18
        /** "Speed" of monster mooving */
        static final byte STEP = 5;
        /** The distance within monster "sees" Marv */
        static final short DISTANCE_ATTACKING = 70;
        static final short DISTANCE_EVADING = 110;
        static final byte TURNING_CORRECTION_STEP = 8;
    //#elif IMGGROUP_24x24 || IMGGROUP_24x24xLimited
        /** "Speed" of monster mooving */
        static final byte STEP = 7;
        /** The distance within monster "sees" Marv */
        static final short DISTANCE_ATTACKING = 93;
        static final short DISTANCE_EVADING = 147;
        static final byte TURNING_CORRECTION_STEP = 11;
    //#elif IMGGROUP_36x36
        /** "Speed" of monster mooving */
        static final byte STEP = 10;
        /** The distance within monster "sees" Marv */
        static final short DISTANCE_ATTACKING = 140;
        static final short DISTANCE_EVADING = 220;
        static final byte TURNING_CORRECTION_STEP = 16;
    //#endif

    static final byte CORRECTION_COUNT = 3;
    static final byte CORRECTION_INCREASING = 1;
    static final byte CORRECTION_DECREASING = -1;
    /**
    * The x coordinate of the left edge of platform
    */
    short leftEdgeBoundary;
    /**
    * The x coordinate of the right edge of platform
    */
    short rightEdgeBoundary;

    public byte modeNext;
    /** 
    * Current step of moving of monster
    */
    private long lStep, lStepX, lPosX, stopDistance;

    private long[] correctionArray;
    private boolean correctionActivated;
    private byte correctionIndex, correctionDirection;

    private int counter;
    
    /**
    * Constructs a JumpingBeast class
    * @param x the x coordinate of monster
    * @param y the y coordinate of monster
    * @param leftEdgeBoundary the x oordinate of the left edge of horizontal platform
    * @param rightEdgeBoundary the x oordinate of the right edge of horizontal platform
    */
    public JumpingBeast(short x, short y, short leftEdgeBoundary, short rightEdgeBoundary) {
        super(x, y);

        lStepX = 0;
        lPosX = FP.toFP(x);
        stopDistance = 0;
      
        this.leftEdgeBoundary = leftEdgeBoundary;
        this.rightEdgeBoundary = rightEdgeBoundary;

        int i;
        long A, B;

        correctionArray = new long[CORRECTION_COUNT + 1];
        for (i = 0; i <= CORRECTION_COUNT; i++) {
            B = FP.div(FP.toFP(i), FP.toFP(CORRECTION_COUNT));
            A = FP.toFP(1) - B;
            correctionArray[i] = FP.div(FP.mul(FP.toFP(2), FP.mul(FP.toFP(CORRECTION_COUNT), FP.mul(A, B))) + FP.mul(FP.toFP(CORRECTION_COUNT), FP.mul(B, B)), FP.toFP(CORRECTION_COUNT));
        }
        correctionActivated = false;
        correctionIndex = 0;

        animations = new Animation[Constants.ANIMATION_BEAST.length];
        for (i = 0; i < animations.length; i++) {
            animations[i] = new Animation(Constants.ANIMATION_BEAST[i]);
        }
        mode = -1;
        setMode(MODE_MOVE_LEFT);
        changeState(STATE_IDLE);
    }

    /**
    * This method makes a "thinking" of this type of monster. Here is implemented AI.
    */
    public void update() {
        int width;

        if (isActive) {
            switch (currentState) {
                case STATE_PATROLING:
                    if (isMarvWithInRange(STATE_EVADING) && (Hero.m_blDynamite || Hero.m_nPickAxe > 0)) {
                        changeState(STATE_EVADING);
                    }
                    else if(isMarvWithInRange(STATE_ATTACKING)) {
                        changeState(STATE_ATTACKING);
                    }
                    else {
                        width = currentAnimation.GetCurFrmWidth() >> 1;
                        if(
                           (mode == MODE_MOVE_LEFT && (posX - width) <= (leftEdgeBoundary + FP.toShort(stopDistance))) ||
                           (mode == MODE_MOVE_RIGHT && (posX + width) >= (rightEdgeBoundary - FP.toShort(stopDistance)))
                          ) {
                            changeState(STATE_IDLE);
                        }
                    }
                break;
            
                case STATE_IDLE:
                    width = currentAnimation.GetCurFrmWidth() >> 1;
                    if (isMarvWithInRange(STATE_EVADING) && (Hero.m_blDynamite || Hero.m_nPickAxe > 0)) {
                        changeState(STATE_EVADING);
                    }
                    else if(isMarvWithInRange(STATE_ATTACKING)) {
                        changeState(STATE_ATTACKING);
                    }
                    else if(--counter == 0) {
                        changeState(STATE_PATROLING);
                        if (mode == MODE_IDLE_LEFT) {
                            goToRight();
                        }
                        else if (mode == MODE_IDLE_RIGHT) {
                            goToLeft();
                        }
                    }
                break;
            
                case STATE_ATTACKING:
                    if (isMarvWithInRange(STATE_EVADING) && (Hero.m_blDynamite || Hero.m_nPickAxe > 0)) {
                        changeState(STATE_EVADING);
                    }
                    else if (isMarvWithInRange(STATE_ATTACKING)) {
                        width = currentAnimation.GetCurFrmWidth() >> 1;
                        if(
                           (mode == MODE_MOVE_LEFT && (posX - width) <= (leftEdgeBoundary + FP.toShort(stopDistance))) ||
                           (mode == MODE_MOVE_RIGHT && (posX + width) >= (rightEdgeBoundary - FP.toShort(stopDistance)))
                          ) {
                            changeState(STATE_IDLE);
                        }
                        else {
                            if (Hero.m_nPosX < posX) {
                                goToLeft();
                            }
                            else if(Hero.m_nPosX > posX) {
                                goToRight();
                            }
                        }
                    }
                    else {
                        changeState(STATE_PATROLING);
                    }
                break;
            
                case STATE_EVADING:
                    if (isMarvWithInRange(STATE_EVADING) && (Hero.m_blDynamite || Hero.m_nPickAxe > 0)) {
                        width = currentAnimation.GetCurFrmWidth() >> 1;
                        if (Hero.m_nPosX < posX) {
                            if ((posX + width) >= (rightEdgeBoundary - FP.toShort(stopDistance))) {
                                setIdleLeft();
                            }
                            else if (mode != MODE_IDLE_LEFT) {
                                goToRight();
                            }
                        }
                        else if (Hero.m_nPosX > posX) {
                            if((posX - width) <= (leftEdgeBoundary + FP.toShort(stopDistance))) {
                                setIdleRight();
                            }
                            else if (mode != MODE_IDLE_RIGHT) {
                                goToLeft();
                            }
                        }
                    }
                    else if (isMarvWithInRange(STATE_ATTACKING)) {
                        changeState(STATE_ATTACKING);
                    }
                    else {
                        changeState(STATE_PATROLING);
                    }
                break;

                case STATE_DEAD:
                break;
            }
            move();
            checkIntersect();
        }
    }
   
    /**
    * Method implements calculation of movements. Here is processing current monsetr mode, processing 
    * moving from left edge of platform to the right one
    */
    private void move() {
        if (correctionActivated) {
            if (correctionDirection == CORRECTION_DECREASING) {
                correctionIndex += correctionDirection;
            }
            lStepX = FP.mul(lStep, correctionArray[correctionIndex]);
            stopDistance += FP.mul(FP.toFP(STEP), correctionArray[correctionIndex]) * correctionDirection;
            if (correctionDirection == CORRECTION_INCREASING) {
                correctionIndex += correctionDirection;
            }
            if (correctionIndex == 0 || correctionIndex == (CORRECTION_COUNT + 1)) {
                correctionActivated = false;
            }
        }
        lPosX += lStepX;

        if (modeNext == MODE_MOVE_LEFT) {
            if (mode == MODE_MOVE_LEFT) {
                if (lStepX == 0) {
                    lPosX -= FP.toFP(TURNING_CORRECTION_STEP);
                    lStep = -FP.toFP(STEP);
                    setIncreasingCorrection();
                }
            }
            else if (mode == MODE_MOVE_RIGHT || mode == MODE_TURN_RIGHT || mode == MODE_IDLE_RIGHT) {
                if (!correctionActivated && lStepX == 0) {
                    lPosX -= FP.toFP(TURNING_CORRECTION_STEP);
                    setMode(MODE_TURN_LEFT);
                }
                else {
                    setDecreasingCorrection();
                }
            }
            else if (mode == MODE_IDLE_LEFT) {
                setMode(MODE_MOVE_LEFT);
            }
        }
        else if (modeNext == MODE_MOVE_RIGHT) {
            if (mode == MODE_MOVE_RIGHT) {
                if (lStepX == 0) {
                    lPosX += FP.toFP(TURNING_CORRECTION_STEP);
                    lStep = FP.toFP(STEP);
                    setIncreasingCorrection();
                }
            }
            else if (mode == MODE_MOVE_LEFT || mode == MODE_TURN_LEFT || mode == MODE_IDLE_LEFT) {
                if (!correctionActivated && lStepX == 0) {
                    lPosX += FP.toFP(TURNING_CORRECTION_STEP);
                    setMode(MODE_TURN_RIGHT);
                }
                else {
                    setDecreasingCorrection();
                }
            }
            else if(mode == MODE_IDLE_RIGHT) {
                setMode(MODE_MOVE_RIGHT);
            }
        }
        else if(modeNext == MODE_IDLE_LEFT) {
            if (mode == MODE_MOVE_LEFT) {
                if (!correctionActivated && lStepX == 0) {
                    setMode(MODE_IDLE_LEFT);
                }
                else {
                    setDecreasingCorrection();
                }
            }
            else if(mode == MODE_MOVE_RIGHT || mode == MODE_IDLE_RIGHT) {
                if (!correctionActivated && lStepX == 0) {
                    setMode(MODE_TURN_LEFT);
                }
                else {
                    setDecreasingCorrection();
                }
            }
        }
        else if (modeNext == MODE_IDLE_RIGHT) {
            if (mode == MODE_MOVE_RIGHT) {
                if (!correctionActivated && lStepX == 0) {
                    setMode(MODE_IDLE_RIGHT);
                }
                else {
                    setDecreasingCorrection();
                }
            }
            else if (mode == MODE_MOVE_LEFT || mode == MODE_IDLE_LEFT) {
                if(!correctionActivated && lStepX == 0) {
                    setMode(MODE_TURN_RIGHT);
                }
                else {
                    setDecreasingCorrection();
                }
            }
        }

        posX = FP.toShort(lPosX);

        currentAnimation.Tick();
      
        if ((currentAnimation.nStateFlg & Animation.cnANIM_STATE_END_OF_CYCLE) == Animation.cnANIM_STATE_END_OF_CYCLE) {
            if (mode == MODE_TURN_LEFT) {
                setMode(MODE_MOVE_LEFT);
            }
            else if(mode == MODE_TURN_RIGHT) {
                setMode(MODE_MOVE_RIGHT);
            }
            else if(mode == MODE_DEATH) {
                isActive = false;
            }
            else if(mode == MODE_IDLE_LEFT || mode == MODE_IDLE_RIGHT) {
                currentAnimation.nStateFlg |= Animation.cnANIM_STATE_PAUSED;
                currentAnimation.SetLastFrame();
            }
        }
    }
   
    /*
     *
     */
    private void setIncreasingCorrection() {
        correctionDirection = CORRECTION_INCREASING;
        if (!correctionActivated) {
            correctionIndex = 0;
            correctionActivated = true;
        }
    }

    /*
     *
     */
    private void setDecreasingCorrection() {
        correctionDirection = CORRECTION_DECREASING;
        if (!correctionActivated) {
            correctionIndex = CORRECTION_COUNT + 1;
            correctionActivated = true;
        }
    }

    private void setMode(byte newMode) {
        if (mode != newMode) {
            mode = newMode;
            switch (mode) {
                case MODE_MOVE_LEFT:
                    currentAnimation = animations[Constants.ANIMATION_BEAST_MOVE_LEFT];
                    currentAnimation.Reset();
                break;

                case MODE_MOVE_RIGHT:
                    currentAnimation = animations[Constants.ANIMATION_BEAST_MOVE_RIGHT];
                    currentAnimation.Reset();
                break;

                case MODE_TURN_LEFT:
                    //#if IMGGROUP_12x12xLimited
                        setMode(MODE_MOVE_LEFT);
                    //#else
                        currentAnimation = animations[Constants.ANIMATION_BEAST_TURN];
                        currentAnimation.nStateFlg |= Animation.cnANIM_STATE_PLAY_REVERSED;
                        currentAnimation.Reset();
                    //#endif
                break;
            
                case MODE_TURN_RIGHT:
                    //#if IMGGROUP_12x12xLimited
                        setMode(MODE_MOVE_RIGHT);
                    //#else
                        currentAnimation = animations[Constants.ANIMATION_BEAST_TURN];
                        currentAnimation.nStateFlg &= ~Animation.cnANIM_STATE_PLAY_REVERSED;
                        currentAnimation.Reset();
                    //#endif
                break;
            
                case MODE_IDLE_LEFT:
                    currentAnimation = animations[Constants.ANIMATION_BEAST_IDLE_LEFT];
                    currentAnimation.Reset();
                break;
            
                case MODE_IDLE_RIGHT:
                    currentAnimation = animations[Constants.ANIMATION_BEAST_IDLE_RIGHT];
                    currentAnimation.Reset();
                break;
            
                case MODE_DEATH:
                    currentAnimation = animations[Constants.ANIMATION_BEAST_DEATH];
                    currentAnimation.Reset();
                break;
            }
        }
    }

    /**
     * Method makes a monster moving to the left
     */
    private void goToLeft() {
        if (modeNext != MODE_MOVE_LEFT) {
            modeNext = MODE_MOVE_LEFT;
        }
    }
    
    /**
     * Method makes a monster moving to the right
     */
    private void goToRight() {
        if (modeNext != MODE_MOVE_RIGHT) {
            modeNext = MODE_MOVE_RIGHT;
        }
    }
    
    /**
     * Sets idle mode on the left side
     */
    private void setIdleLeft() {
        if (modeNext != MODE_IDLE_LEFT) {
            modeNext = MODE_IDLE_LEFT;
        }
    }
    
    /**
     * Sets idle mode on the right side
     */
    private void setIdleRight() {
        if (modeNext != MODE_IDLE_RIGHT) {
            modeNext = MODE_IDLE_RIGHT;
        }
    }
    
    /**
     * Method process on exit state
     * @param prevState the previous State
     */
    public void onExitState(byte prevState) {
    }
    
    /**
     * Method process on enter state
     * @param newState the new State
     */
    public void onEnterState(byte newState) {
        switch (newState) {
            case STATE_PATROLING:
                counter = 0;

                int width = currentAnimation.GetCurFrmWidth() >> 1;
                if (mode == MODE_IDLE_LEFT || modeNext == MODE_IDLE_LEFT) {
                    if ((posX - width) <= (leftEdgeBoundary + FP.toShort(stopDistance))) {
                        goToRight();
                    }
                    else {
                        goToLeft();
                    }
                }
                else if (mode == MODE_IDLE_RIGHT || modeNext == MODE_IDLE_RIGHT) {
                    if ((posX + width) >= (rightEdgeBoundary - FP.toShort(stopDistance))) {
                        goToLeft();
                    }
                    else {
                        goToRight();
                    }
                }
            break;
         
            case STATE_IDLE:
                counter = 30;
                if (mode == MODE_TURN_LEFT || mode == MODE_MOVE_LEFT) {
                    setIdleLeft();
                }
                else if (mode == MODE_TURN_RIGHT || mode == MODE_MOVE_RIGHT) {
                    setIdleRight();
                }
            break;
         
            case STATE_ATTACKING:
            break;
         
            case STATE_EVADING:
            break;

            case STATE_DEAD:
                lStep = 0;
                setMode(MODE_DEATH);
            break;
        }
    }
    
    /**
     * Checks if Slime can reach Marv
     */
    private boolean isMarvWithInRange(byte checkState) {
        int areaLeft, areaTop, areaRight, areaBottom, distance;
        switch (checkState) {
            case STATE_ATTACKING:
                distance = DISTANCE_ATTACKING;
            break;

            case STATE_EVADING:
                distance = DISTANCE_EVADING;
            break;

            default:
                distance = 0;
            break;
        }
        areaLeft   = posX - distance;
        if (areaLeft < leftEdgeBoundary) areaLeft = leftEdgeBoundary;
        areaTop = posY - (currentAnimation.GetCurFrmHeight());
        areaRight  = posX + distance;
        if (areaRight > rightEdgeBoundary) areaRight = rightEdgeBoundary;
        areaBottom = posY;

        int marvLeft   = Hero.m_nPosX - (Hero.animationCurrent.GetCurFrmWidth() >> 1);
        int marvTop    = Hero.m_nPosY - 1;
        int marvRight  = Hero.m_nPosX + (Hero.animationCurrent.GetCurFrmWidth() >> 1);
        int marvBottom = Hero.m_nPosY;

        switch (checkState) {
            case STATE_ATTACKING:
                return Hero.m_nDeathTicks == 0 && Hero.m_nImmortalTicks == 0 && Hero.m_nKeybMode == Hero.nModeNormal && 
                       leftEdgeBoundary < marvRight && marvLeft < rightEdgeBoundary && 
                       marvLeft < areaRight && marvRight > areaLeft && posY >= marvBottom && posY <= (marvBottom + 2);

            case STATE_EVADING:
                return Hero.m_nDeathTicks == 0 && Hero.m_nImmortalTicks == 0 && Hero.m_nKeybMode == Hero.nModeNormal && 
                       marvLeft < areaRight && marvRight > areaLeft && marvTop < areaBottom && marvBottom > areaTop;

            default:
                return false;
        }
    }
   
   /**
    *
    */
    public void checkIntersect() {
        if (currentState != STATE_DEAD) {
            int monsterLeft   = posX - (currentAnimation.GetCurFrmHeight() >> 1);
            int monsterRight  = posX + (currentAnimation.GetCurFrmHeight() >> 1);
            
            int marvLeft   = Hero.m_nPosX - (Hero.animationCurrent.GetCurFrmWidth() >> 1);
            int marvRight  = Hero.m_nPosX + (Hero.animationCurrent.GetCurFrmWidth() >> 1);
            int marvBottom = Hero.m_nPosY;

            boolean isIntersected = Hero.m_nDeathTicks == 0 && Hero.m_nImmortalTicks == 0 && leftEdgeBoundary < marvRight && marvLeft < rightEdgeBoundary;

            isIntersected = isIntersected && Hero.m_bJumpEnable && (Hero.m_nKeybMode == Hero.nModeNormal) && marvLeft < monsterRight && marvRight > monsterLeft && posY >= marvBottom && posY <= (marvBottom + 2);

            if (isIntersected) {
                if (Hero.m_blDynamite || (Hero.m_nPickAxe > 0)) {
                    changeState(STATE_DEAD);
                    Hero.monsterKilled(Constants.TYPE_JUMPINGBEAST);
                }
                else {
                    Hero.ForceDeath(Hero.c_nAnimID_Death);
                    changeState(STATE_IDLE);
                }
            }
        }
    }
}
